import * as _ from 'lodash';

export type ValidationRule<T> = (t: T) => void;

export function and<T>(...rules: ValidationRule<T>[]): ValidationRule<T> {
  if (rules.length < 1) {
    throw new Error('no and rules');
  }
  return (t: T) => rules.forEach((rule) => rule(t));
}

export function or<T>(...rules: ValidationRule<T>[]): ValidationRule<T> {
  if (rules.length < 1) {
    throw new Error('no and rules');
  }
  return (t: T) => {
    let firstError = undefined;
    for (const rule of rules) {
      try {
        rule(t);
        return;
      } catch (err) {
        firstError = firstError || err;
      }
    }
    throw firstError;
  };
}

export class ValidationError extends Error {
  constructor(message: string) {
    super(message);
  }
}

export const notNilThen = function <T>(message: string, then: ValidationRule<T>): ValidationRule<T | undefined | null> {
  return (s: T | undefined | null) => {
    if (s === null || s === undefined) {
      throw new ValidationError(message);
    }
    then(s);
  };
};

export const notNil = function (message: string) {
  return {
    then: function <T>(then: ValidationRule<T>): ValidationRule<T | undefined | null> {
      return (s: T | undefined | null) => {
        if (s === null || s === undefined) {
          throw new ValidationError(message);
        }
        then(s);
      };
    },
  };
};

export const str = {
  notEmpty(message: string): ValidationRule<string> {
    return (s: string) => {
      if (_.isEmpty(s)) {
        throw new ValidationError(message);
      }
    };
  },
  notBlank(message: string): ValidationRule<string> {
    return (s: string) => {
      if (_.isEmpty(_.trim(s))) {
        throw new ValidationError(message);
      }
    };
  },
};
